/*
 *
 * Copyright(c) 2018-2019 chj <1019487871@qq.com>
 * QQ: 1019487871
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 *
 */
#include "los.h"
#include "../component/fundation/buffer/los_buffer.h"
#include "../component/fundation/timer/los_timer.h"

#define LOS_TIMER_TASK_STACT_SIZE           (1024U)
#define LOS_TIMER_TASK_PRIO                 (LOS_THREAD_X_BIT*LOS_THREAD_Y_BIT-10)
#define LOS_TIMER_MUTEX_PRIO                (LOS_TIMER_TASK_PRIO-10)

static struct los_timer_cb_t
{
    struct losq_t             timerq;
    struct los_sem_lcb        sem;
    struct los_mutex_lcb      lock;
}los_timer;

enum los_timer_state_e los_timer_get_state(struct los_timer_t *timer)
{
    if(timer==NULL)
        return LOS_TIMER_ERROR;
    else
        return timer->state;
}

void los_timer_stop(struct los_timer_t *timer)
{
    los_mutex_pend(&los_timer.lock,LOS_TIMEROUT_FOREVER);
    losq_remove(&los_timer.timerq, &timer->node);
    timer->state = LOS_TIMER_STOP;
    los_mutex_post(&los_timer.lock);
}

void los_timer_start(struct los_timer_t *timer)
{
    struct los_timer_t *tmp_timer = NULL;
    timer->node.addr = timer->interval + los_get_time_ms();
    tmp_timer = los_timer.timerq.head;
    los_timer_stop(timer);

    los_mutex_pend(&los_timer.lock,LOS_TIMEROUT_FOREVER);
    losq_enqueue(&los_timer.timerq, &timer->node);
    timer->state = LOS_TIMER_RUNNIG;
    if((timer==(struct los_timer_t *)los_timer.timerq.head) && (tmp_timer!=timer))
        los_sem_post(&los_timer.sem);
    los_mutex_post(&los_timer.lock);
}

struct los_timer_t *los_timer_create(uint32_t interval, int16_t trigger_cnt,
                                        void (*cb)(void *), void *param)
{
    struct los_timer_t *timer = los_malloc(sizeof(struct los_timer_t));

    if (NULL == timer)
    {
        return NULL;
    }
    timer->interval = interval;
    timer->trigger_cnt = trigger_cnt;
    timer->cb = cb;
    timer->param = param;
    timer->state = LOS_TIMER_INIT;
    return timer;
}

void los_timer_free(struct los_timer_t *timer)
{
    los_timer_stop(timer);
    los_free(timer);
}

static bool_t los_timer_should_trigger(uint32_t cur_tick, uint32_t timer)
{
    return cur_tick >= timer ?
            (cur_tick - timer) < (1UL << (sizeof(uint32_t) * 8 - 1)) :
            (timer - cur_tick) >= (1UL << (sizeof(uint32_t) * 8 - 1));
}

static uint32_t los_timer_thread(void)
{
    uint32_t cur_tick = 0;
    uint32_t delay_ms = 0;
    struct los_timer_t *timer = NULL;
    while(1)
    {
        cur_tick = los_get_time_ms();
        timer = (struct los_timer_t *)los_timer.timerq.head;
        while(timer != NULL)
        {
            if(los_timer_should_trigger(cur_tick, timer->node.addr))
            {
                timer->state = LOS_TIMER_ISR;
                if(timer->cb != NULL)
                {
                    timer->cb(timer->param);
                }
                if (timer->trigger_cnt > 0)
                {
                    timer->trigger_cnt--;
                }
                if (timer->trigger_cnt != 0)
                {
                    los_timer_start(timer);
                }
                else
                {
                    if(timer->state == LOS_TIMER_ISR)
                        los_timer_stop(timer);
                }
                timer = (struct los_timer_t *)los_timer.timerq.head;
            }
            else
            {
                break;
            }
        }
        timer = (struct los_timer_t *)los_timer.timerq.head;
        if(timer==NULL)
        {
            los_sem_pend(&los_timer.sem,LOS_TIMEROUT_FOREVER);
            timer = (struct los_timer_t *)los_timer.timerq.head;
        }
        cur_tick = los_get_time_ms();
        delay_ms = timer->node.addr > cur_tick ?
                (timer->node.addr - cur_tick) : (CN_LIMIT_SINT32 - cur_tick + timer->node.addr);
        los_sem_pend(&los_timer.sem,delay_ms*LOS_MS);
    }
    return 0;
}

void los_timer_init(void)
{
    void *thread = NULL;
    losq_init(&los_timer.timerq);
    if(NULL==los_sem_create(&los_timer.sem,1,0))
        goto SEM_INIT_ERR;
    if(NULL==los_mutex_create(&los_timer.lock,LOS_TIMER_MUTEX_PRIO))
        goto MUTEX_INIT_ERR;
    if( (thread = los_resiger_thread(los_timer_thread,NULL,LOS_TIMER_TASK_STACT_SIZE,LOS_TIMER_TASK_PRIO)) == NULL)
        goto TASK_ERR;
    else
    {
        los_start_thread(thread);
        return;
    }
TASK_ERR:
//    mutex_del(los_timer.lock);
MUTEX_INIT_ERR:
//    semp_del(los_timer.sem);
SEM_INIT_ERR:
    return;
}

